package com.softaria.spkiller.layers.classificator.impl;


import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.xml.bind.JAXBElement;


import com.softaria.spkiller.jaxb.configuration.OpAnd;
import com.softaria.spkiller.jaxb.configuration.OpNot;
import com.softaria.spkiller.jaxb.configuration.OpOr;
import com.softaria.spkiller.jaxb.configuration.PredAnnotated;
import com.softaria.spkiller.jaxb.configuration.PredClassName;
import com.softaria.spkiller.jaxb.configuration.PredExtends;
import com.softaria.spkiller.jaxb.configuration.PredImplements;
import com.softaria.spkiller.jaxb.configuration.PredPackageName;
import com.softaria.spkiller.jaxb.configuration.Predicate;
import com.softaria.spkiller.jaxb.configuration.Rule;
import com.softaria.spkiller.layers.LayerIdentifier;
import com.softaria.spkiller.layers.LayerIdentifierImpl;
import com.softaria.spkiller.layers.classificator.Classificator;
import com.softaria.spkiller.layers.classificator.impl.AbstractTermPredicate.TermMode;
import com.softaria.spkiller.metadata.AnnotationIdentifier;
import com.softaria.spkiller.metadata.ClassIdentifier;
import com.softaria.spkiller.metadata.ClassMetaData;
import com.softaria.spkiller.metadata.extractor.MetaDataExtractor;

public class SoftariaClassificator implements Classificator,ClassProbe {

	private class ClassificationRule{
		private ClassificationPredicate predicate;
		private LayerIdentifier layer;
		
		
		public ClassificationRule(ClassificationPredicate predicate,
				LayerIdentifier layer) {
			super();
			this.predicate = predicate;
			this.layer = layer;
		}


		public ClassificationPredicate getPredicate() {
			return predicate;
		}


		public LayerIdentifier getLayer() {
			return layer;
		}
		
		
	}
	
	private static final String JAVA_LANG_OBJECT = "java.lang.Object";
	private static final String COM_SOFTARIA_SPKILLER_ANNOTATIONS_LAYER = "com.softaria.spkiller.annotations.Layer";
	private MetaDataExtractor extractor;
	private IOProblemCollector collector;
	
	private List<ClassificationRule> rules = new ArrayList<SoftariaClassificator.ClassificationRule>();
	
	
	public SoftariaClassificator(MetaDataExtractor extractor,IOProblemCollector collector) {
		this.extractor = extractor;
		this.collector = collector;
		
		
	}
	
	public void addClassificationRule(ClassificationPredicate predicate,LayerIdentifier layer) {
		rules.add(new ClassificationRule(predicate, layer));
	}
	
	
	public void loadConfiguration(List<Rule> rules) {
		for(Rule rule:rules) {
			Predicate pred = rule.getPredicate().getValue();
			
			ClassificationPredicate cPred = parsePredicate(pred);
			
			addClassificationRule(cPred,new LayerIdentifierImpl(rule.getLayer()));
		}
	}

	private ClassificationPredicate parsePredicate(Predicate pred) {
		if(pred instanceof OpAnd) {
			OpAnd opAnd = (OpAnd)pred;
			
			List<JAXBElement<? extends Predicate>> arguments = opAnd.getPredicate();
			
			AndPredicate and = new AndPredicate();
			
			for(JAXBElement<? extends Predicate> argument:arguments) {
				ClassificationPredicate operand = parsePredicate(argument.getValue());
				
				and.addOperand(operand);
			}
			
			return and;
		}

		if(pred instanceof OpOr) {
			OpOr opOr = (OpOr)pred;
			
			List<JAXBElement<? extends Predicate>> arguments = opOr.getPredicate();
			
			OrPredicate or = new OrPredicate();
			
			for(JAXBElement<? extends Predicate> argument:arguments) {
				ClassificationPredicate operand = parsePredicate(argument.getValue());
				
				or.addOperand(operand);
			}
			
			return or;
		}
		
		if(pred instanceof OpNot) {
			OpNot opNot = (OpNot)pred;
			
			ClassificationPredicate notPred = parsePredicate(opNot.getPredicate().getValue());
			
			NotPredicate not = new NotPredicate(notPred);
			
			return not;
		}
		
		if(pred instanceof PredExtends) {
			PredExtends predExtends = (PredExtends)pred;
			
			return new ExtendsClassPredicate(predExtends.getClazz());
		}

		if(pred instanceof PredImplements) {
			PredImplements predImplements = (PredImplements)pred;
			
			return new ImplementsInterfacePredicate(predImplements.getInterface(),predImplements.isDirectly());
		}
		
		if(pred instanceof PredClassName) {
			PredClassName predClassName = (PredClassName)pred;
			
			return new ClassNamePredicate(predClassName.getTerm(),parseMode(predClassName.getCondition()));
		}
		
		if(pred instanceof PredPackageName) {
			PredPackageName predPackageName = (PredPackageName)pred;
			
			return new PackageNamePredicate(predPackageName.getTerm(),parseMode(predPackageName.getCondition()));
		}
		
		if(pred instanceof PredAnnotated) {
			PredAnnotated predAnnotated = (PredAnnotated)pred;
			
			return new AnnotationExistsPredicate(predAnnotated.getWith());
		}
		
		throw new RuntimeException("Unknown class of predicate "+pred.getClass().getName());
		
	}

	private TermMode parseMode(
			com.softaria.spkiller.jaxb.configuration.TermMode mode) {
		switch(mode) {
		case CONTAINS:
			return TermMode.contains;
		case ENDS_WITH:
			return TermMode.endsWith;
		case STARTS_WITH:
			return TermMode.startsWith;
		case EQUALS:
			return TermMode.equals;
		}
		
		throw new RuntimeException();
	}

	@Override
	public LayerIdentifier classificate(ClassIdentifier classID) {

		ClassMetaData metaData;
		try {
			metaData = extractor.getMetaData(classID);
			
			if(metaData==null) {
				collector.reportClassNotFound(classID);
				return null;
			}
			
			return classificate(metaData);
			
		} catch (IOException e) {
			collector.reportIoException(classID,e);
			
			return null;
		}

		
	}

	

	@Override
	public LayerIdentifier classificate(ClassMetaData metaData) {
		Collection<AnnotationIdentifier> annotations = metaData.getAnnotations();
		
		if(annotations==null) return null;
		
		for(AnnotationIdentifier id:annotations) {
			if(id.getAnnotationClass().equals(COM_SOFTARIA_SPKILLER_ANNOTATIONS_LAYER)) {
				Object layerName = id.getValue();
				
				if(layerName!=null) {
					return new LayerIdentifierImpl((String)layerName);
				}
			}
		}
		
		for(ClassificationRule rule:rules) {
			try {
				if(rule.getPredicate().matches(metaData, this)) {
					return rule.getLayer();
				}
			} catch (IOException e) {
				collector.reportIoException(metaData.getIdentifier(), e);
			}
		}
		
		return null;
		
	}

	/* (non-Javadoc)
	 * @see com.softaria.spkiller.layers.classificator.impl.ClassProbe#annotatedWith(com.softaria.spkiller.metadata.ClassMetaData, java.lang.String)
	 */
	@Override
	public boolean annotatedWith(ClassMetaData metaData, String annotation) {
		
		Collection<AnnotationIdentifier> annotations = metaData.getAnnotations();
		
		if(annotations==null) return false;
		
		for(AnnotationIdentifier id:annotations) {
			if(id.getAnnotationClass().equals(annotation)) {
				return true;
			}
		}
		
		return false;
	}
	
	/* (non-Javadoc)
	 * @see com.softaria.spkiller.layers.classificator.impl.ClassProbe#implementsInterface(com.softaria.spkiller.metadata.ClassMetaData, java.lang.String)
	 */
	@Override
	public boolean implementsInterface(ClassMetaData metaData, String inter_face,boolean directly) throws IOException {
		if(directly) {
			return implementsInterfaceDirectly(metaData, inter_face);
		}
		else {
			boolean direct = implementsInterfaceDirectly(metaData, inter_face);
			
			if(direct) {
				return true;
			}
			else {
				ClassIdentifier superclass = metaData.getSuperclass();
				
				if((superclass!=null)&&(superclass.getClassName().equals(JAVA_LANG_OBJECT))) {
					ClassMetaData parentMD = extractor.getMetaData(metaData.getSuperclass());
					
					if(parentMD == null) {
						return false;
					}
					
					return implementsInterface(parentMD, inter_face, false);
				}
				else {
					return false;
				}
				
				
			}
		}
	}

	private boolean implementsInterfaceDirectly(ClassMetaData metaData,
			String inter_face) {
		Collection<ClassIdentifier> interfaces = metaData.getInterfaces();
		
		if(interfaces==null) return false;
		
		for(ClassIdentifier id:interfaces) {
			if(id.getClassName().equals(inter_face)) {
				return true;
			}
		}
		
		return false;
	}
	
	/* (non-Javadoc)
	 * @see com.softaria.spkiller.layers.classificator.impl.ClassProbe#extendsClass(com.softaria.spkiller.metadata.ClassMetaData, java.lang.String)
	 */
	@Override
	public boolean extendsClass(ClassMetaData metaData,String superclass) throws IOException {
		if(metaData.getSuperclass()==null) {
			return false;
		}
		
		if(metaData.getSuperclass().getClassName().equals(JAVA_LANG_OBJECT)) {
			return false;
		}
		
		if(metaData.getSuperclass().getClassName().equals(superclass)) {
			return true;
		}
		
		ClassMetaData parentMD = extractor.getMetaData(metaData.getSuperclass());
		
		if(parentMD==null) {
			return false;
		}
		
		return extendsClass(parentMD, superclass);
		
	}

}
